// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package csv

import (
	"bufio"
	"io"
	"strings"
	"unicode"
	"unicode/utf8"
)

// 写入程序使用CSV编码写入记录。
// 
// 由NewWriter返回，Writer写入以
// 换行符结尾的记录，并使用“，”作为字段分隔符。导出的字段可以是
// 更改为在第一次调用Write或WriteAll之前自定义详细信息。
// 
// 逗号是字段分隔符。
// 
// 如果UseCRLF为真，则写入程序将以\r\n而不是\n结束每个输出行。
// 
// 缓冲单个记录的写入。
// 写入所有数据后，客户端应调用
// Flush方法，以确保所有数据都已转发到
// 底层io。作家任何发生的错误都应该通过调用Error方法进行检查。
type Writer struct {
	Comma   rune // 字段分隔符（设置为“，”by NewWriter）
	UseCRLF bool // True使用\r\n作为行终止符
	w       *bufio.Writer
}

// NewWriter返回一个写入w的新Writer。
func NewWriter(w io.Writer) *Writer {
	return &Writer{
		Comma: ',',
		w:     bufio.NewWriter(w),
	}
}

// Write将一条CSV记录连同任何必要的引号一起写入w。
// 记录是一段字符串，每个字符串都是一个字段。
// 写操作是缓冲的，因此最终必须调用Flush以确保将记录写入基础io。作家
func (w *Writer) Write(record []string) error {
	if !validDelim(w.Comma) {
		return errInvalidDelim
	}

	for n, field := range record {
		if n > 0 {
			if _, err := w.w.WriteRune(w.Comma); err != nil {
				return err
			}
		}

		// 如果我们不需要一个带引号的字段，那么只需
		// 写出该字段，然后继续下一个字段。
		if !w.fieldNeedsQuotes(field) {
			if _, err := w.w.WriteString(field); err != nil {
				return err
			}
			continue
		}

		if err := w.w.WriteByte('"'); err != nil {
			return err
		}
		for len(field) > 0 {
			// 搜索特殊字符。
			i := strings.IndexAny(field, "\"\r\n")
			if i < 0 {
				i = len(field)
			}

			// 逐字复制特殊字符之前的所有内容。
			if _, err := w.w.WriteString(field[:i]); err != nil {
				return err
			}
			field = field[i:]

			// 对特殊字符进行编码。
			if len(field) > 0 {
				var err error
				switch field[0] {
				case '"':
					_, err = w.w.WriteString(`""`)
				case '\r':
					if !w.UseCRLF {
						err = w.w.WriteByte('\r')
					}
				case '\n':
					if w.UseCRLF {
						_, err = w.w.WriteString("\r\n")
					} else {
						err = w.w.WriteByte('\n')
					}
				}
				field = field[1:]
				if err != nil {
					return err
				}
			}
		}
		if err := w.w.WriteByte('"'); err != nil {
			return err
		}
	}
	var err error
	if w.UseCRLF {
		_, err = w.w.WriteString("\r\n")
	} else {
		err = w.w.WriteByte('\n')
	}
	return err
}

// Flush将所有缓冲数据写入底层io。作家
// 要检查刷新过程中是否发生错误，请调用error。
func (w *Writer) Flush() {
	w.w.Flush()
}

// 错误报告上一次写入或刷新期间发生的任何错误。
func (w *Writer) Error() error {
	_, err := w.w.Write(nil)
	return err
}

// WriteAll使用Write将多个CSV记录写入w，然后调用Flush，
// 从Flush返回任何错误。
func (w *Writer) WriteAll(records [][]string) error {
	for _, record := range records {
		err := w.Write(record)
		if err != nil {
			return err
		}
	}
	return w.w.Flush()
}

// fieldNeedsQuotes报告我们的字段是否必须用引号括起来。
// 带逗号的字段、带引号或换行符的字段，以及
// 以空格开头的字段必须用引号括起来。
// 我们以前引用空字符串，但现在不再引用了（从Go 1.4开始）。
// 这两种表示应该是等效的，但Postgres在数据库导入期间区分了
// 带引号的空字符串和非带引号的空字符串，并且它有
// 一个选项来强制非带引号的CSV的带引号的行为，但它有
// 没有选项来强制带引号的CSV的不带引号的行为，使带引号的空字符串的
// CSV变得不那么有用。
// 不引用空字符串也会使此包与Microsoft Excel和Google Drive的行为
// 相匹配。
// 对于Postgres，请引用数据终止字符串“\”。
func (w *Writer) fieldNeedsQuotes(field string) bool {
	if field == "" {
		return false
	}

	if field == `\.` {
		return true
	}

	if w.Comma < utf8.RuneSelf {
		for i := 0; i < len(field); i++ {
			c := field[i]
			if c == '\n' || c == '\r' || c == '"' || c == byte(w.Comma) {
				return true
			}
		}
	} else {
		if strings.ContainsRune(field, w.Comma) || strings.ContainsAny(field, "\"\r\n") {
			return true
		}
	}

	r1, _ := utf8.DecodeRuneInString(field)
	return unicode.IsSpace(r1)
}
